package com.xbongbong.paas.redis;

import com.xbongbong.pro.config.ProBaseConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author 吴峰
 * @date 2020/12/04 11:05
 */
@Component
public class PaasRedisLock {

    private static final Logger LOGGER = LoggerFactory.getLogger(PaasRedisLock.class);

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    /**
     * 超时时间 30分钟
     */
    public static final int TIMEOUT = 30;

    @Resource
    private RedisCacheAutoConfiguration redisCacheAutoConfiguration;

    /**
     * 获取锁。在 timeout 时间内如果获取失败失败，会重试
     * @param prefix 前缀
     * @param lockKey 锁key
     * @param requestId key 对应的 值，用来防止多个客户端获取到同一个锁。可以用UUID来产生
     * @param expire 锁超时时间。防止由于异常情况下，锁一直未被释放
     * @param timeout 获取锁超时时间，超时时间内，可以重试
     * @return 锁获取成功，返回true；否则，返回false
     */
    public boolean tryLock(String prefix, String lockKey, String requestId, int expire, int timeout, TimeUnit unit) {

        final long start = System.currentTimeMillis();
        final long end = start + calcSeconds(timeout, unit);
        if (getLock(prefix,lockKey, requestId, expire, unit)) {
            return true;
        }
        // 默认返回失败
        boolean res = false;
        // 循环不断重试获取锁，直至获取锁成功或者超时
        while(!(res = getLock(prefix,lockKey, requestId, expire, unit))) {
            // 超时，仍未获取到锁，不再重试，返回
            if(System.currentTimeMillis() > end) {
                break;
            }

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return res;
    }

    /**
     * 时间转换成毫秒
     * @param time
     * @param unit
     * @return
     */
    private long calcSeconds (long time, TimeUnit unit){
        if (unit == TimeUnit.DAYS) {
            return time * 24 * 60 * 60 * 1000;
        } else if (unit == TimeUnit.HOURS){
            return time * 60 * 60 * 1000;
        } else  if (unit == TimeUnit.MINUTES) {
            return time * 60 * 1000;
        } else if (unit == TimeUnit.SECONDS){
            return time * 1000;
        } else {
            return time;
        }
    }
    /**
     * 获取锁。在 timeout 时间内如果获取失败失败，会重试
     * @param prefix  前缀
     * @param lockKey 锁key
     * @param requestId key 对应的 值，用来防止多个客户端获取到同一个锁。可以用UUID来产生
     * @param expire 锁超时时间。防止由于异常情况下，锁一直未被释放
     * @param unit 时间单位
     * @return 锁获取成功，返回true；否则，返回false
     */
    public boolean getLock(String prefix, String lockKey, String requestId, int expire, TimeUnit unit) {
        String key = ProBaseConfig.getProjectName() + ":" + prefix + ":" + lockKey;
        return stringRedisTemplate.opsForValue().setIfAbsent(key, requestId, expire, unit);
    }
    /**
     * 释放锁
     * @param prefix  前缀
     * @param lockKey 锁key
     * @param requestId key 对应的 值，用来防止多个客户端获取到同一个锁。可以用UUID来产生
     *
     */
    public void unlock(String prefix, String lockKey, String requestId) {
        List<String> keys = Arrays.asList(ProBaseConfig.getProjectName() + ":" + prefix + ":" + lockKey);
        Object luaExistKeyDel = stringRedisTemplate.execute(redisCacheAutoConfiguration.businessRuleUnLock(), keys, String.valueOf(requestId));
        if(Objects.equals(1L, luaExistKeyDel)) {
            LOGGER.info("解锁成功!");
        } else {
            // TODO
            LOGGER.error("@@@ 解锁失败!");
        }
    }
}
